#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nyu_object.h"
#include "nyu_string.h"

NyuObject *root_object = NULL;
long root_object_val = 0;
NyuString *strval_proto;

static NyuString *to_string(NyuObject *self);
static guint get_hash_code(NyuObject *self);
static gboolean equals(NyuObject *self, NyuObject *other);

NyuObject *nyu_object_new()
{
    NyuObject *self;

    self = nyu_refcnt_new(NyuObject);
    if (!self) {
        perror("cannot alloc NyuObject!!");
        abort();
    }

    NYU_OBJECT_INIT(self, NYU_OBJECT_TYPE_OBJECT, nyu_object_destroy);

    self->to_string = to_string;
    self->get_hash_code = get_hash_code;
    self->equals = equals;

    nyu_refcnt_ref(root_object);
    g_ptr_array_add(self->slot_array, (gpointer)root_object_val);
    g_hash_table_insert(self->slots, nyu_refcnt_ref(strval_proto),
                        GINT_TO_POINTER(self->slot_array->len));

    return self;
}

void nyu_object_destroy(NyuObject *self)
{
    guint i;

    if (!self)
        return;

    for (i = 0; i < self->slot_array->len; i++) {
        long v = (long)g_ptr_array_index(self->slot_array, i);
        if ((v & 3) == 2)
            nyu_refcnt_unref(NYU_REFCNT(v ^ 2));
    }

    g_ptr_array_free(self->slot_array, TRUE);
    g_hash_table_unref(self->slots);
}

void nyu_object_put_slot(NyuObject *self, NyuObject *key, long val)
{
    gpointer ptr;

    if ((val & 3) == 2)
        nyu_refcnt_ref(NYU_REFCNT(val ^ 2));

    if ((ptr = g_hash_table_lookup(self->slots, key))) {
        guint idx = GPOINTER_TO_INT(ptr) - 1;
        long old = (long)g_ptr_array_index(self->slot_array, idx);
        if ((old & 3) == 2)
            nyu_refcnt_unref(NYU_REFCNT(old ^ 2));
        g_ptr_array_index(self->slot_array, idx) = (gpointer)val;
    } else {
        g_ptr_array_add(self->slot_array, (gpointer)val);
        ptr = GINT_TO_POINTER(self->slot_array->len);
        g_hash_table_insert(self->slots, key, ptr);
    }
}

static NyuObject *nyu_object_get_slot_owner(NyuObject *self, NyuObject *key)
{
    NyuObject *proto;

    if (g_hash_table_lookup(self->slots, key))
        return self;

    /* slot_arrayの先頭は常に__proto__ */
    proto = NYU_OBJECT((long)g_ptr_array_index(self->slot_array, 0) & ~3);
    if (!proto)
        return NULL;

    return nyu_object_get_slot_owner(proto, key);
}

void nyu_object_set_slot(NyuObject *self, NyuObject *key, long val)
{
    //g_hash_table_insert(self->slots, key, (gpointer)val);
}

long nyu_object_get_slot(NyuObject *self, NyuObject *key)
{
    gpointer ptr;
    NyuObject *proto;

    if ((ptr = g_hash_table_lookup(self->slots, key))) {
        return (long)g_ptr_array_index(self->slot_array, GPOINTER_TO_INT(ptr)-1);
    }

    /* slot_arrayの先頭は常に__proto__ */
    proto = NYU_OBJECT((long)g_ptr_array_index(self->slot_array, 0) & ~3);
    if (!proto)
        return (long)0;

    return nyu_object_get_slot(proto, key);
}

long *nyu_object_get_slot_lvalue(NyuObject *self,
                                 NyuObject *key,
                                 gboolean update,
                                 gboolean create)
{
    gpointer ptr;
    NyuObject *owner;

    if ((ptr = g_hash_table_lookup(self->slots, key))) {
        return (long *)&g_ptr_array_index(self->slot_array,
                                          GPOINTER_TO_INT(ptr)-1);
    } else if (update && (owner = nyu_object_get_slot_owner(self, key))) {
        ptr = g_hash_table_lookup(owner->slots, key);
        return (long *)&g_ptr_array_index(owner->slot_array,
                                          GPOINTER_TO_INT(ptr)-1);
    } else if (create) {
        g_ptr_array_add(self->slot_array, ptr);
        ptr = GINT_TO_POINTER(self->slot_array->len);
        g_hash_table_insert(self->slots, key, ptr);
        return (long *)&g_ptr_array_index(self->slot_array, GPOINTER_TO_INT(ptr)-1);
    }

    return NULL;
}

NyuString *nyu_object_to_string(NyuObject *self)
{
    return self->to_string(self);
}

guint nyu_object_get_hash_code(NyuObject *self)
{
    return self->get_hash_code(self);
}

gboolean nyu_object_equals(NyuObject *self, NyuObject *other)
{
    return self->equals(self, other);
}

static NyuString *to_string(NyuObject *self)
{
    return nyu_string_new("TEST");
}

static guint get_hash_code(NyuObject *self)
{
    return (guint)((long)self);
}

static gboolean equals(NyuObject *self, NyuObject *other)
{
    return (self == other);
}
